/*=========================================================================

     portable.cpp  -  Copyright (C) 1993-2005 by Don Cross

     Contains main() for a completely portable (stdio.h UI)
     version of NewChess.

=========================================================================*/

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#include "chess.h"
#include "gamefile.h"
#include "chenga.h"
#include "uistdio.h"
#include "lrntree.h"


int AnalyzeGameFile ( 
    double thinkTimeInSeconds, 
    const char *inGameFilename, 
    const char *outListFilename );

const char * const CHENARD_VERSION = ConvertDateToVersion(__DATE__);

static void Advertise()
{
    printf ( "\nChenard - a portable chess program, v %s", CHENARD_VERSION);
    printf ( "\n" );
    printf ( "by Don Cross  -  http://cosinekitty.com/chenard/\n" );

    #if defined(__linux__)
        #if defined(__GNUG__)
            printf ( "Linux version; compiled with GNU C++ %s\n\n", __VERSION__ );
        #else
            printf ( "Linux version; unknown compiler used.\n");
        #endif
    #elif defined(WIN32)
        #if defined(_MSC_VER)
            printf ( "Win32 version; compiled with Microsoft Visual C++ %0.1lf\n", (double)(_MSC_VER - 600)/100.0);
        #else
            printf ( "Win32 version; unknown compiler used.\n");
        #endif
    #else
        printf ( "(Unknown operating system build - may have incorrect behavior.)\n");
    #endif
}


int main ( int argc, const char *argv[] )
{
    char line [256];
    ChessUI_stdio  theUserInterface;
    ChessBoard     theBoard;

    Advertise();

    if ( argc == 1 ||
         (argc>1 && argv[1][0] != '-') )
    {
        const char *gameFilename = "chess.gam";
        if ( argc == 2 )
            gameFilename = argv[1];
        else
            remove ( gameFilename );   // start over!

        if ( gameFilename )
        {
            // Load the game from the given filename.
            if ( LoadGame ( theBoard, gameFilename ) )
                printf ( "Continuing game in file '%s'\n", gameFilename );
            else
                printf ( "Will auto-save new game to '%s'\n", gameFilename );
        }

        ChessGame theGame ( theBoard, theUserInterface );

        if ( gameFilename )
        {
            if ( !theGame.AutoSaveToFile ( gameFilename ) )
            {
                fprintf (
                    stderr,
                    "Error:  Auto-save '%s' option has failed (out of memory?)\x07\n",
                    gameFilename );

                return 1;
            }
        }

        theGame.Play();
    }
    else if ( argc >= 2 )
    {
        if ( strncmp ( argv[1], "-t", 2 ) == 0 )
        {
            // Tree trainer

            int timeToThink = atoi(argv[1]+2);
            if ( timeToThink < 1 ) {
                timeToThink = 10;
            }

            ComputerChessPlayer *whitePlayer = new ComputerChessPlayer (theUserInterface);
            ComputerChessPlayer *blackPlayer = new ComputerChessPlayer (theUserInterface);

            whitePlayer->SetTimeLimit ( INT32(timeToThink) * INT32(100) );
            blackPlayer->SetTimeLimit ( INT32(timeToThink) * INT32(100) );

            whitePlayer->SetOpeningBookEnable ( false );
            blackPlayer->SetOpeningBookEnable ( false );

            whitePlayer->SetTrainingEnable ( false );
            blackPlayer->SetTrainingEnable ( false );

            theUserInterface.SetScoreDisplay ( true );

            Learn_Output = 1;

            LearnTree tree;
            for ( int argindex=2; argindex < argc; ++argindex ) {
                tree.absorbFile ( argv[argindex] );
            }
            
            tree.trainer (
                whitePlayer,
                blackPlayer,
                theBoard,
                theUserInterface,
                INT32(timeToThink) * INT32(100) 
            );

            delete whitePlayer;
            delete blackPlayer;
        }
        else if ( strncmp ( argv[1], "-s", 2 ) == 0 )
        {
            // Self play

            int timeToThink = atoi(argv[1]+2);
            if ( timeToThink < 1 )
                timeToThink = 10;

            ComputerChessPlayer *whitePlayer =
                new ComputerChessPlayer ( theUserInterface );

            ComputerChessPlayer *blackPlayer =
                new ComputerChessPlayer ( theUserInterface );

            if ( !whitePlayer || !blackPlayer )
                ChessFatal ( "Out of memory!\n" );

            whitePlayer->SetTimeLimit ( INT32(timeToThink) * INT32(100) );
            blackPlayer->SetTimeLimit ( INT32(timeToThink) * INT32(100) );

            whitePlayer->setExtendedSearchFlag ( true );
            blackPlayer->setExtendedSearchFlag ( true );

            theUserInterface.SetScoreDisplay ( true );

            unsigned long uniqueTag = 1;
            extern const char *LogFilename;
            const char *tagFilename = "chenard.id";
            FILE *tagSaveFile = fopen ( tagFilename, "rt" );
            if ( tagSaveFile )
            {
                if ( fscanf ( tagSaveFile, "%lu", &uniqueTag ) != 1 )
                    uniqueTag = 1;

                fclose ( tagSaveFile );
                tagSaveFile = 0;
            }

            tagSaveFile = fopen ( tagFilename, "wt" );
            if ( !tagSaveFile )
            {
                sprintf ( line, 
                    "Cannot open unique tag file '%s' for write!\n", 
                    tagFilename );

                ChessFatal ( line );
            }

            fprintf ( tagSaveFile, "%lu\n", 1+uniqueTag );
            fclose ( tagSaveFile );

            static char uniqueFilename [256];
            sprintf ( uniqueFilename, "self%04lu.txt", uniqueTag );
            LogFilename = uniqueFilename;

            ChessGame theGame (
                theBoard,
                theUserInterface,
                false,     // white is played by a ComputerChessPlayer object
                false,     // black is played by a ComputerChessPlayer object
                whitePlayer,
                blackPlayer );

            // When playing against self, improve training by telling
            // one of the players (randomly) to not use opening book.

            if ( ChessRandom(2) )
                whitePlayer->SetOpeningBookEnable ( false );
            else
                blackPlayer->SetOpeningBookEnable ( false );

            theGame.Play();
            Advertise();
        }
        else if ( strcmp ( argv[1], "-g" ) == 0 )
        {
            // Genetic algorithm...

            theUserInterface.SetScoreDisplay (false);
            ChessGA  ga (theUserInterface, 1);

            if ( !ga.load() )
            {
                printf ( "!!!!  Cannot load existing gene pool.\n" );
                printf ( "!!!!  Enter desired gene pool size: " );
                fflush (stdout);
                int population = 0;
                if ( !fgets(line,sizeof(line),stdin) || sscanf(line,"%d",&population)!=1 )
                    return 1;

                ga.createRandomPopulation (population);
                ga.save();
            }

            ga.run();
        }
        else if ( strcmp ( argv[1], "-b" ) == 0 )
        {
            // Battle two genes against each other...
            if ( argc < 3 )
            {
                fprintf ( stderr, 
                    "Gene battle usage:\n\n"
                    "    chenard -b genefile1 [genefile2]\n\n" );

                return 1;
            }

            const char *genFn1 = argv[2];
            const char *genFn2 = (argc > 3) ? argv[3] : 0;
            int rc = GeneBattle ( theUserInterface, genFn1, genFn2 );
            return rc;
        }
        else if ( strcmp ( argv[1], "-G" ) == 0 )
        {
            if ( argc < 5 )
            {
                fprintf ( stderr, 
                    "Gene pool merge usage:\n\n"
                    "    chenard -G poolsize otherdir1 otherdir2 ...\n\n" );

                return 1;
            }

            // Slurp "cream of the crop" from other GAs into current directory.
            int outputPoolSize = atoi(argv[2]);

            return ChessGA::MergePools ( 
                outputPoolSize, 
                argc-3,
                argv+3,
                theUserInterface,
                1 );
        }
#ifndef __linux__
        else if ( strcmp ( argv[1], "-E" ) == 0 )
        {
            extern int EditLearnTree();
            return EditLearnTree();
        }
#endif // __linux__
        else if ( strcmp ( argv[1], "-p" ) == 0 )
        {
            SCORE window = 0;
            if ( argc>=3 && (window=atoi(argv[2])) >= 50 )
            {
                const char *inTreeFilename  = DEFAULT_CHENARD_TREE_FILENAME;
                const char *outTreeFilename = "packed.trx";
                printf ( "Packing experience tree file...\n" );
                printf ( "   Input  filename = '%s'\n", inTreeFilename );
                printf ( "   Output filename = '%s'\n", outTreeFilename );
                return LearnTree::Pack ( inTreeFilename, outTreeFilename, window );
            }
            else
            {
                fprintf ( stderr,
                    "Use:  %s -p scoreWindow\n\nwhere 'scoreWindow' is at least 50\n\n",
                    argv[0] );

                return 0;
            }
        }
        else if ( strcmp ( argv[1], "-a" ) == 0 )
        {
            // chenard -a thinkTimeSeconds inGameFile outListingFile

            if ( argc != 5 )
            {
                fprintf ( stderr, 
                    "Use:  %s -a thinkTimeSeconds inGameFile outListingFile\n\n", 
                    argv[0] );

                return 0;
            }

            const double thinkTimeSeconds = atof ( argv[2] );
            if ( thinkTimeSeconds < 0.1 )
            {
                fprintf ( stderr,
                    "Invalid think time '%s'\n",
                    argv[2] );

                return 1;
            }

            const char *inGameFilename = argv[3];
            const char *outListFilename = argv[4];
            return AnalyzeGameFile ( thinkTimeSeconds, inGameFilename, outListFilename );
        }
        else
        {
            fprintf ( stderr, 
                "Use:  %s [-s<time_per_move_in_seconds>]\n", argv[0] );

            fprintf ( stderr, 
                "The option '-s' causes Chenard to play against itself.\n" );

            return 1;
        }
    }

    return 0;
}


class ChessUI_stdio_InterceptBestPath: public ChessUI_stdio
{
public:
    void DisplayBestPath ( const ChessBoard &, const BestPath & );
    BestPath snagBestPath;
    FILE *listFile;     // !!! temp debug
};


void ChessUI_stdio_InterceptBestPath::DisplayBestPath (
    const ChessBoard &board,
    const BestPath &path )
{
    snagBestPath = path;
    ChessUI_stdio::DisplayBestPath ( board, path );

#if 0 // !!! temp debug
    if (listFile) {
        fprintf ( listFile, "\n{ " );
        ChessBoard temp = board;
        Move move;
        UnmoveInfo unmove;
        char moveString [64];
        for ( int i=0; i <= path.depth; ++i )
        {
            move = path.m[i];
            FormatChessMove ( temp, move, moveString );
            if ( i > 0 ) fprintf ( listFile, ", " );
            fprintf ( listFile, "%s", moveString );
            temp.MakeMove ( move, unmove );
        }
        fprintf ( listFile, "}\n" );
    }
#endif
}


void AnalyzePosition ( 
    ChessUI_stdio_InterceptBestPath &ui,
    ComputerChessPlayer &thinker,
    ChessBoard &_board, 
    FILE *listFile,
    int column )
{
    INT32 timeSpent = 0;
    Move bestMove;
    bool gotMove = thinker.GetMove ( _board, bestMove, timeSpent );

    char moveString [64];
    ChessBoard board = _board;

    const BestPath &path = ui.snagBestPath;
    if ( path.depth >= 0 )
    {
        column += fprintf ( listFile, "[%d: ", int(bestMove.score) );

        for ( int i=0; i <= path.depth; i++ )
        {
            Move move = path.m[i];
            UnmoveInfo unmove;

            FormatChessMove ( board, move, moveString );
            board.MakeMove ( move, unmove );

            int newcol = (int) (column + strlen(moveString) + 2);
            if ( newcol > 78 )
            {
                fprintf ( listFile, "\n" );
                column = fprintf ( listFile, "                   " );
            }

            column += fprintf ( listFile, "%s", moveString );

            if ( i < path.depth )
                column += fprintf ( listFile, ", " );
        }

        column += fprintf ( listFile, "]" );
    }
}


int AnalyzeGameFile ( 
    double thinkTimeInSeconds, 
    const char *inGameFilename, 
    const char *outListFilename )
{
    FILE *gameFile = fopen ( inGameFilename, "rb" );
    if ( !gameFile )
    {
        fprintf ( stderr, "Error:  Cannot open game file '%s' for read\n\n", inGameFilename );
        return 1;
    }

    fclose (gameFile);  // close file, because stream object will open it again
    gameFile = NULL;

    FILE *listFile = fopen ( outListFilename, "wt" );
    if ( !listFile )
    {
        fprintf ( stderr, "Error:  Cannot open list file '%s' for write\n\n", outListFilename );
        return 2;
    }

    fprintf ( listFile, "Chenard auto-analysis of game file '%s'\n", inGameFilename );
    fprintf ( listFile, "Think time = %0.2lf seconds.\n\n", thinkTimeInSeconds );

    ChessUI_stdio_InterceptBestPath ui;
    ui.listFile = NULL; // !!! temp debug: causes thinking output to be dumped to output file
    ui.SetScoreDisplay (true);

    ComputerChessPlayer thinker(ui);
    thinker.SetTrainingEnable (false);
    thinker.setExtendedSearchFlag (false);
    thinker.setResignFlag (false);
    thinker.SetTimeLimit ( INT32(100.0 * thinkTimeInSeconds) );

    ChessBoard board;
    Move move;
    UnmoveInfo unmove;
    int moveNumber = 0;
    char moveString [64];

    tChessMoveStream *stream = tChessMoveStream::OpenFileForRead (inGameFilename);
    if (stream) {
        bool reset = false;
        for ( int i=0; stream->GetNextMove(move,reset); ++i )
        {
            if (reset) {
                board.Init();
                fprintf (listFile, "\n----------- NEW GAME -----------\n\n");
            }
            ui.DrawBoard (board);
            int column = 0;
            if ( (i & 1) == 0 )
                column += fprintf ( listFile, "%4d. ", ++moveNumber );
            else
                column += fprintf ( listFile, "      " );

            if ( (move.dest & SPECIAL_MOVE_MASK) == SPECIAL_MOVE_EDIT )
            {
                fprintf ( listFile, "*** board was edited -- ending analysis.\n", i );
                break;
            }

            FormatChessMove ( board, move, moveString );
            column += fprintf ( listFile, "%-7s  ", moveString );
            ui.snagBestPath.depth = -1;
            AnalyzePosition ( ui, thinker, board, listFile, column );
            fprintf ( listFile, "\n" );
            if ( i & 1 )
                fprintf ( listFile, "\n" );
            fflush ( listFile );
            board.MakeMove ( move, unmove );
        }

        delete stream;
        stream = NULL;
    } else {
        fprintf (stderr, "Error:  Failure to create stream object for '%s'\n", inGameFilename);
        return 3;
    }

    fclose ( listFile );

    return 0;
}
